#!/usr/bin/python

import socket, string, time, twitter, sys, getopt, os, logging, urllib2
from threading import Thread

# Ubuntu/Debian sets the default character encoding as ascii, and then 
# hides the sys.setdefaultencoding method in /etc/python*/site.py
# Reloading the sys module re-exposes this method, allowing encoding to 
# be set to utf-8. This fixes the exceptions due to writing utf-8 characters.
reload(sys) 
sys.setdefaultencoding("utf-8")

class Twitter(Thread):
	def __init__(self, username, password, interval = 120):
		self.username = username
		Thread.__init__(self)
	
		self.setDaemon(True)
		self.interval = interval  # How often to check for updates

		self.messagequeue = []
		self.following = []

		self.twitter = twitter.Api(username = username, password = password)

		self.following = self._getfollowing()
		self.twitter.SetSource("peepd")
		
	def run(self):
		self._testLogin()

		while True:
			self._fetchmessages()
			time.sleep(self.interval)

	def _testLogin(self):
		try:
			self.twitter.GetUser(self.username)
		except urllib2.HTTPError:
			print "Whoops, not authenticated"

	def _getfollowing(self):
		users = self.twitter.GetFriends()
		return users
	
	def getfollowinglist(self):
		return [u.screen_name for u in self.following]

	def _fetchmessages(self):
		try:
			messages = self.twitter.GetFriendsTimeline()
		except urllib2.HTTPError, e:
			print e
			print "HTTP Error while trying to fetch messages"
			messages = []

		for m in messages:
			self.messagequeue.insert(0, m)

	def readmessagequeue(self, num = 0):
		if num == 0:
			num = len(self.messagequeue)

		return_list = []
		for i in range(num):
			return_list.append(self.messagequeue.pop(0))

		return return_list

	def post(self, message):
		if len(message) > 140:
			return False
		status = self.twitter.PostUpdate(message)

		return status

class IRCHandler(Thread):
	def __init__(self, client_socket):
		Thread.__init__(self)

		self.socket = client_socket
		self.sh = self.socket.makefile('wb')

		self.nick = None
		self.user = None
		self.userhost = socket.gethostbyaddr(self.socket.getpeername()[0])[0]

		self.servername = "localhost.localdomain"
		self.inchannel = False

		self.quit = False
		self.twitter = None

		self.seenmessages = []

	def run(self):
		print "New connection - spinning up thread!"
		self.sh.write("NOTICE AUTH :Twitter IRC Gateway initialized, please do go on.\n")
		self.sh.flush()
		while not self.quit:
			self.read()
			
			if self.twitter:
				messages = self.twitter.readmessagequeue()
			
				for message in messages:
					self._writemessage(message)
				
		self.sh.close()
		self.socket.close()

	def _writemessage(self, message):
		if message.id not in self.seenmessages:
			text = message.text.replace("\n", " ")
			text = text.replace("&gt;", ">")
			text = text.replace("&lt;", "<")

			self.seenmessages.insert(0, message.id)
			if len(self.seenmessages) > 60:
				# Just keep the last 60 messages, that should be enough...
				self.seenmessages.pop()

			self._write("PRIVMSG #Twitter :%s" % text, source = "%s!%s@twitter.com" % (message.user.screen_name, message.user.screen_name))

	def read(self):
		data = self.sh.readline().strip()
		data = string.split(data, ":")

		command = string.split(data[0])
		command.append(data[1:])

		methods = getattr(self.__class__, "__dict__")

		if command[0] in methods:
			m = methods[command[0]]
			apply(m, [self, command])

	def _write(self, line, source = None):
		if source == None:
			source = self.servername

		self.sh.write(":%s %s\n" % (source, line))
		self.sh.flush()

	def _printWelcome(self):
		# This should only ever be called once per connection
		if self.nick == None:
			print "Woah, we shouldn't be calling _printWelcome() yet"

		self._write("001 %s :Welcome to the Twitter IRC Gateway, %s" % (self.nick, self.nick))
		self._write("002 %s :Your host is a badly coded python IRC server, running version 1" % (self.nick))
		self._write("003 %s :This server was created on %s" % (self.nick, time.strftime("%A, %d %b %Y %H:%M:%S%Z", time.localtime())))
		self._write("004 %s :CHANTYPES=#& PREFIX=(ov)@+ CHANMODES=eIb,k,l,imnpst NETWORK=PY CASEMAPPING=rfc1459 CHARSET=ascii :are not really supported by this server" % self.nick)

		self.twitter = Twitter(self.nick, self.twitterpass)
		self.twitter.start()
		
	def _forceJoin(self, channel):
		self._write("JOIN :%s" % channel, source = "%s!%s@%s" % (self.nick, self.user, self.userhost))
		self.JOIN(["JOIN", "%s" % channel])

	def WHO(self, command):
		# TODO 
		pass

	def PASS(self, command):
		self.twitterpass = command[1]

	def NICK(self, command):
		# The user wants to change his nick
		oldnick = self.nick

		self.nick = command[1]

		if oldnick == None:
			# We're a new connection, print out the welcome notice!
			self._printWelcome()

	def USER(self, command):
		self.user = command[1]

		if not self.inchannel:
			self._forceJoin("#Twitter")
			

	def PING(self, command):
		self._write("PONG localhost.localdomain :localhost.localdomain")

	def JOIN(self, command):
		if self.inchannel == False:
			channel = command[1]
			self._write("353 %s = %s :%s TwitterBot" % (self.nick, channel, self.nick))
			self._write("353 %s = %s :%s" % (self.nick, channel, " ".join(self.twitter.getfollowinglist())))
			self._write("366 %s %s :End of /NAMES list" % (self.nick, channel))
			self._write("328 %s %s :http://twitter.com" % (self.nick, channel))
			self._write("332 %s %s :Welcome to #Twitter. Twit on!" % (self.nick, channel))
			self._write("333 %s %s TwitterBot %s" % (self.nick, channel, time.time()))
			self._write("MODE #Twitter +t")
			
			self.inchannel = True

	def PRIVMSG(self, command):
		channel = command[1]
		message = command[2][0]

		if channel == "#Twitter":
			status = self.twitter.post(message)

			if status == False:
				# TODO
				print "Warning: Message not sent. Could be too long?"

			self.seenmessages.insert(0, status.id)

	def AWAY(self, command):
		if command[1] == ['']:
			self._write("305 %s :Welcome back" % (self.nick))
		else:
			self._write("306 %s :You're now marked as away: %s" % (self.nick, command[1]))

	def QUIT(self, command):
		# Let's finish up now
		self.quit = True


def usage():
	print """%s, a twitter/irc gateway. 

Usage: %s [OPTIONS]
	
    -p, --port <port number     Specify the port number to listen on, default 6667
    -a, --address <ip address>  Specify the IP address to listen on, default 127.0.0.1
    -d, --no-daemon             Don't detach and run as a daemon
    -P, --pid-file              Specify a PID file to use
    -h, --help                  Print this message
""" % (sys.argv[0], sys.argv[0])

def fork(pid_file = None):

	if pid_file == None:
		pid_file = "/tmp/%s.lock" % sys.argv[0]

	try:
		pid = os.fork()
		if pid > 0:
			# First parent should exit
			sys.exit(0)
	except OSError, e:
		sys.stderr.write("Fork failed: %d (%s)\n" % (e.errno, e.strerror))
		sys.exit(1)
    
	os.chdir("/") # Make sure we don't keep any filesystem from mounting
	os.setsid()
	os.umask(0)

	try:
		# Do second fork
		pid = os.fork()
		if pid > 0:
			print "Starting up in daemon mode, pid %d" % os.getpid()
			open(pid_file, "w").write("%d" % pid)
			sys.exit(0)
	except OSError, e:
		sys.stderr.write("Fork failed: %d (%s)\n" % (e.errno, e.strerror))
		sys.exit(1)

if __name__ == "__main__":
	PORT = 6667
	ADDR = "127.0.0.1"
	NOFORK = False

	try:
		opt, arg = getopt.getopt(sys.argv[1:], "p:a:dh", ["port=", "address=", "no-daemon", "help"])
	except getopt.GetoptError, err:
		print str(err)
		usage()
		sys.exit(1)
	
	for o, a in opt:
		if o in ("-h", "--help"):
			usage()
			sys.exit(0)
		elif o in ("-p", "--port"):
			PORT = int(a)
		elif o in ("-a", "--address"):
			ADDR = a
		elif o in ("-d", "--no-daemon"):
			NOFORK = True
		elif o in ("-P", "--pid-file"):
			pid_file = a
	

	if not NOFORK:
		fork()

	server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	try:
		server.bind((ADDR, PORT))
	except socket.error:
		sys.stderr.write("Oops, I just tried to bind on a socket that's being used. Bailing out!\n")
		sys.exit(1)

	server.listen(5)
	
	try:
		while True:
			(client, client_addr) = server.accept()
	
			client_thread = IRCHandler(client)
			client_thread.start()
	
	except KeyboardInterrupt:
		print "Interrput received, I'm outta here."
		server.close()
		sys.exit(0)
	

